# coding=utf-8
u"""F5 Networks® LBaaSv2 Driver Implementation."""
# Copyright 2016 F5 Networks Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

import os
import sys
import time

from oslo_config import cfg
from oslo_log import helpers as log_helpers
from oslo_log import log as logging
from oslo_utils import importutils

from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.plugins.common import constants as plugin_constants
from neutron_lib.api.definitions import portbindings
from neutron_lib import constants as q_const
from neutron_lib.plugins import constants as pg_const
from neutron_lib.plugins import directory

from neutron_lbaas.db.loadbalancer import models

from f5_driver import agent_rpc
from f5lbaasdriver.v2.bigip import constants_v2 as constants
from f5lbaasdriver.v2.bigip import exceptions as f5_exc
from f5lbaasdriver.v2.bigip import neutron_client
from f5lbaasdriver.v2.bigip import plugin_rpc
from neutron_lib import context as ncontext
from neutron_lib import constants as n_const

LOG = logging.getLogger(__name__)


# these configurations are defined by
# /etc/neutron/neutron_lbaas.conf
OPTS = [
    cfg.IntOpt(
        'f5_driver_perf_mode',
        default=0,
        help=('switch driver performance mode from 0 to 3')
    ),
    cfg.BoolOpt(
        'to_speedup_populate_logic',
        default=False,
        help=("If True, uses new fast populate logic,"
              "If set to False, then revert to old behavior "
              "just in case.")
    ),
    cfg.StrOpt(
        'f5_loadbalancer_pool_scheduler_driver_v2',
        default=(
            'f5lbaasdriver.v2.bigip.agent_scheduler.TenantScheduler'
        ),
        help=('Driver to use for scheduling '
              'pool to a default loadbalancer agent')
    ),
    cfg.ListOpt(
        'agent_filters',
        default=[
            'EnvironmentFilter,RandomFilter'
        ],
        help=('Filters of Agent scheduler')
    ),
    cfg.StrOpt(
        'f5_loadbalancer_service_builder_v2',
        default=(
            'f5lbaasdriver.v2.bigip.service_builder.LBaaSv2ServiceBuilder'
        ),
        help=('Default class to use for building a service object.')
    ),
    cfg.StrOpt(
        'bwc_profile',
        default=None,
        help='bwc_profile name which is configured in bigip side'
    ),
    cfg.StrOpt(
        'unlegacy_setting_placeholder_driver_side',
        default=None,
        help=('used in certain hpb cases to differenciate legacy scenarios')
    )
]

cfg.CONF.register_opts(OPTS)


class F5NoAttachedLoadbalancerException(f5_exc.F5LBaaSv2DriverException):
    """Exception thrown when an LBaaSv2 object has not parent Loadbalancer."""

    message = "Entity has no associated loadbalancer"

    def __str__(self):
        return self.message


class F5DriverV2(object):
    u"""F5 Networks® LBaaSv2 Driver."""

    def __init__(self, plugin=None, env=None):
        """Driver initialization."""
        if not plugin:
            LOG.error('Required LBaaS Driver and Core Driver Missing')
            sys.exit(1)

        self.plugin = plugin
        # NOTE(qzhao): Append L3 plugin to LBaaSv2 DB plugin
        if not getattr(self.plugin.db, '_l3_plugin', None):
            self.plugin.db._l3_plugin = directory.get_plugin(pg_const.L3)

        self.env = env

        self.loadbalancer = LoadBalancerManager(self)
        self.listener = ListenerManager(self)
        self.pool = PoolManager(self)
        self.member = MemberManager(self)
        self.healthmonitor = HealthMonitorManager(self)
        self.l7policy = L7PolicyManager(self)
        self.l7rule = L7RuleManager(self)

        self.unlegacy_setting_placeholder_driver_side = \
            cfg.CONF.unlegacy_setting_placeholder_driver_side

        # what scheduler to use for pool selection
        self.scheduler = importutils.import_object(
            cfg.CONF.f5_loadbalancer_pool_scheduler_driver_v2)

        self.service_builder = importutils.import_object(
            cfg.CONF.f5_loadbalancer_service_builder_v2, self)

        self.agent_rpc = agent_rpc.LBaaSv2AgentRPC(self)
        self.plugin_rpc = plugin_rpc.LBaaSv2PluginCallbacksRPC(self)

        self.q_client = \
            neutron_client.F5NetworksNeutronClient(self.plugin)


class EntityManager(object):
    '''Parent for all managers defined in this module.'''

    def __init__(self, driver):
        self.driver = driver
        self.model = None

    def _log_entity(self, entity):
        LOG.debug("Log the entity: %s", entity.to_api_dict())

    def _handle_entity_error(self, context, id, **kwargs):
        status = kwargs.get("status", plugin_constants.ERROR)
        lb_id = kwargs.get("loadbalancer_id", None)
        if lb_id:
            self.driver.plugin.db.update_status(context, models.LoadBalancer,
                                                lb_id, status)
        if self.model:
            self.driver.plugin.db.update_status(context, self.model, id,
                                                status)

    def _agent_rpc_topic(self, agent):
        environment_prefix = agent["configurations"][
            "environment_prefix"]
        agent_topic = \
            constants.TOPIC_LOADBALANCER_AGENT_V2 + "_" \
            + environment_prefix + "." + agent["host"]
        return agent_topic

    def _check_provisioning_status(self, lb,
                                   timeout=60,
                                   status="PENDING_CREATE"):
        timeout =  time.time() + timeout
        while True:
            context = ncontext.get_admin_context()
            if "PENDING" not in lb.provisioning_status:
                # race
                self.driver.plugin.db.update_loadbalancer_provisioning_status(
                    context, lb.id, status
                )
                return
            if time.time() > timeout:
                msg = "Timeout for loadbalancer %s" % lb.__dict__
                raise Exception(msg)

            # race
            lb = self.driver.plugin.db.get_loadbalancer(
                    context, lb.id)

    def _call_rpc(self, context, loadbalancer, entity, api_dict,
                  rpc_method, **kwargs):
        '''Perform operations common to create and delete for managers.'''

        try:
            agent_topic, service = self._setup_crud(
                context, loadbalancer, entity, **kwargs)
            rpc_callable = getattr(self.driver.agent_rpc, rpc_method)
            the_port = kwargs.get("the_port_id", None)
            LOG.info(the_port)
            if the_port:
                LOG.info('the_port is not None')
                rpc_callable(
                    context, api_dict, service,
                    agent_topic, the_port_id=the_port
                )
            else:
                LOG.info('the_port is None')
                rpc_callable(context, api_dict, service, agent_topic)
        except Exception as e:
            LOG.error("Exception: %s: %s" % (rpc_method, e))
            raise e

    def _setup_crud(self, context, loadbalancer, entity, **kwargs):
        '''Setup CRUD operations for managers to make calls to agent.

        :param context: auth context for performing CRUD operation
        :param entity: neutron lbaas entity -- target of the CRUD operation
        :returns: tuple -- (agent object, service dict)
        :raises: F5NoAttachedLoadbalancerException
        '''

        if entity.attached_to_loadbalancer() and loadbalancer:
            agent = self._schedule_agent(context, loadbalancer,
                                         entity, **kwargs)
            service = self._create_service(context, loadbalancer, agent,
                                           entity, **kwargs)
            agent_topic = self._agent_rpc_topic(agent)
            return agent_topic, service

        raise F5NoAttachedLoadbalancerException()

    def _schedule_agent(self, context, loadbalancer,
                        entity=None, **kwargs):
        '''Schedule agent --used for most managers.

        :param context: auth context for performing crud operation
        :returns: agent object
        '''

        agent = self.driver.scheduler.schedule(
            self.driver.plugin,
            context,
            loadbalancer.id,
            self.driver.env
        )
        return agent

    def _create_service(self, context, loadbalancer, agent,
                        entity=None, **kwargs):
        '''build service--used for most managers.

        :param context: auth context for performing crud operation
        :returns: service dict
        '''

        service = self.driver.service_builder.build(
            context, loadbalancer, agent, **kwargs)
        return service

    @log_helpers.log_method_call
    def _append_listeners(self, context, service, listener):

        if not listener:
            return

        def get_db_listener():
            if cfg.CONF.f5_driver_perf_mode == 3:
                return listener
            else:
                return self.driver.plugin.db.get_listener(
                    context, listener.id)

        db_listener = get_db_listener()
        listener_dict = db_listener.to_dict(
            loadbalancer=False,
            default_pool=False,
            l7_policies=False
        )

        # Listener may have l7policies or default pool
        listener_dict['l7_policies'] = \
            [{'id': l7_policy.id} for l7_policy in listener.l7_policies]
        if listener.default_pool:
            listener_dict['default_pool_id'] = listener.default_pool.id

        LOG.debug("append listener %s", listener_dict)
        service['listeners'].append(listener_dict)

    @log_helpers.log_method_call
    def _append_pools_monitors(self, context, service, pool):

        if not pool:
            return

        def get_db_pool():
            if cfg.CONF.f5_driver_perf_mode == 3:
                return pool
            else:
                return self.driver.plugin.db.get_pool(
                    context, pool.id)

        db_pool = get_db_pool()

        pool_dict = db_pool.to_dict(
            healthmonitor=False,
            listener=False,
            listeners=False,
            loadbalancer=False,
            l7_policies=False,
            members=False,
            session_persistence=False
        )

        pool_dict['members'] = [{'id': member.id} for member in pool.members]
        pool_dict['l7_policies'] = [
            {'id': l7_policy.id} for l7_policy in pool.l7_policies]

        if pool.session_persistence:
            pool_dict['session_persistence'] = (
                pool.session_persistence.to_api_dict()
            )

        LOG.debug("append pool %s", pool_dict)
        service['pools'].append(pool_dict)

        if not pool.healthmonitor:
            return

        def get_db_healthmonitor():
            if cfg.CONF.f5_driver_perf_mode == 3:
                return pool.healthmonitor
            else:
                return self.driver.plugin.db.get_healthmonitor(
                    context,
                    pool.healthmonitor.id
                )

        healthmonitor = get_db_healthmonitor()
        healthmonitor_dict = healthmonitor.to_dict(pool=False)
        healthmonitor_dict['pool_id'] = pool.id

        LOG.debug("append healthmonitor_dict %s", healthmonitor_dict)
        service['healthmonitors'].append(healthmonitor_dict)


class LoadBalancerManager(EntityManager):
    """LoadBalancerManager class handles Neutron LBaaS CRUD."""

    def __init__(self, driver):
        super(LoadBalancerManager, self).__init__(driver)
        self.model = models.LoadBalancer

    @log_helpers.log_method_call
    def recreate(self, context, loadbalancer):
        """recreate a loadbalancer."""

        self._log_entity(loadbalancer)

        driver = self.driver
        try:
            self._check_provisioning_status(loadbalancer)
            service = {}
            agent = self._schedule_agent(
                context, loadbalancer)
            agent_host = agent['host']
            agent_config = agent.get('configurations', {})
            LOG.debug("agent configurations: %s" % agent_config)

            scheduler = self.driver.scheduler
            agent_config_dict = \
                scheduler.deserialize_agent_configurations(agent_config)

            if not agent_config_dict.get('nova_managed', False):
                # Update the port for the VIP to show ownership by this driver
                port_data = {
                    'admin_state_up': True,
                    'device_owner': 'network:f5lbaasv2',
                    'status': q_const.PORT_STATUS_ACTIVE
                }
                port_data[portbindings.HOST_ID] = agent_host
                if self.driver.unlegacy_setting_placeholder_driver_side:
                    LOG.debug('setting to normal')
                    port_data[portbindings.VNIC_TYPE] = "normal"
                else:
                    LOG.debug('setting to baremetal')
                    port_data[portbindings.VNIC_TYPE] = "baremetal"

                port_data[portbindings.PROFILE] = {}

                llinfo = agent_config_dict.get('local_link_information', None)
                if llinfo:
                    port_data[portbindings.PROFILE] = {
                        "local_link_information": llinfo
                    }

                self.driver.plugin.db._core_plugin.update_port(
                    context,
                    loadbalancer.vip_port_id,
                    {'port': port_data}
                )

                # NOTE(qzhao): Vlan id might be assigned after updating vip
                # port. Need to build service payload after updating port.
                service = self._create_service(context, loadbalancer, agent)

                if driver.unlegacy_setting_placeholder_driver_side:
                    LOG.debug('calling extra build():')
                    service = self.driver.service_builder.build(
                        context, loadbalancer, agent)
            else:
                LOG.debug("Agent devices are nova managed")

            agent_topic = self._agent_rpc_topic(agent)
            driver.agent_rpc.create_loadbalancer(
                context, loadbalancer.to_api_dict(), service, agent_topic,
            )
        except Exception as e:
            LOG.error("Exception: loadbalancer create: %s" % e.message)
            self._handle_entity_error(context, loadbalancer.id)
            raise e


class ListenerManager(EntityManager):
    """ListenerManager class handles Neutron LBaaS listener CRUD."""

    def __init__(self, driver):
        super(ListenerManager, self).__init__(driver)
        self.model = models.Listener

    @log_helpers.log_method_call
    def recreate(self, context, listener):
        self.create(context, listener)

    @log_helpers.log_method_call
    def create(self, context, listener):
        """Create a listener."""

        self._log_entity(listener)

        lb = listener.loadbalancer
        self._check_provisioning_status(lb, status="PENDING_UPDATE")
        api_dict = listener.to_dict(loadbalancer=False, default_pool=False)

        def append_listeners(context, loadbalancer, service):
            self._append_listeners(context, service, listener)

        def append_pools_monitors(context, loadbalancer, service):
            if listener.default_pool:
                for pool in loadbalancer.pools:
                    if pool.id == listener.default_pool.id:
                        self._append_pools_monitors(context, service, pool)
                        break

        try:
            if cfg.CONF.f5_driver_perf_mode in (2, 3):
                # Listener may have default pool who are already created.
                # Utilize default behavior to append members
                # Listener does not have l7policies.
                self._call_rpc(
                    context, lb, listener, api_dict, 'create_listener',
                    append_listeners=append_listeners,
                    append_pools_monitors=append_pools_monitors,
                    append_l7policies_rules=lambda *args: None
                )
            else:
                self._call_rpc(context, lb, listener, api_dict,
                               'create_listener')
        except Exception as e:
            LOG.error("Exception: listener create: %s" % e.message)
            self._handle_entity_error(context, listener.id,
                                      loadbalancer_id=lb.id)
            raise e


class PoolManager(EntityManager):
    """PoolManager class handles Neutron LBaaS pool CRUD."""

    def __init__(self, driver):
        super(PoolManager, self).__init__(driver)
        self.model = models.PoolV2

    def _get_pool_dict(self, pool):
        pool_dict = pool.to_dict(
            healthmonitor=False,
            listener=False,
            listeners=False,
            loadbalancer=False,
            l7_policies=False,
            members=False,
            session_persistence=False)

        if pool.session_persistence:
            pool_dict['session_persistence'] = (
                pool.session_persistence.to_api_dict())

        pool_dict['provisioning_status'] = pool.provisioning_status
        pool_dict['operating_status'] = pool.operating_status
        return pool_dict

    @log_helpers.log_method_call
    def recreate(self, context, listener):
        self.create(context, listener)

    @log_helpers.log_method_call
    def create(self, context, pool):
        """Create a pool."""

        self._log_entity(pool)

        lb = pool.loadbalancer
        self._check_provisioning_status(lb, status="PENDING_UPDATE")
        api_dict = self._get_pool_dict(pool)

        def append_listeners(context, loadbalancer, service):
            for listener in loadbalancer.listeners:
                if listener.default_pool:
                    if listener.default_pool.id == pool.id:
                        LOG.debug("listener %s has default pool %s",
                                  listener.id, pool.id)
                        self._append_listeners(context, service, listener)

        def append_pools_monitors(context, loadbalancer, service):
            self._append_pools_monitors(context, service, pool)

        try:
            if cfg.CONF.f5_driver_perf_mode in (2, 3):
                # Pool and l7plicies ???
                # Pool may be associated with listener, maybe not.
                # Pool has no members
                # Listener may have l7policies. Utilize default behavior.
                self._call_rpc(
                    context, lb, pool, api_dict, 'create_pool',
                    append_listeners=append_listeners,
                    append_pools_monitors=append_pools_monitors,
                    append_members=lambda *args: None
                )
            else:
                self._call_rpc(context, lb, pool, api_dict, 'create_pool')
        except Exception as e:
            LOG.error("Exception: pool create: %s" % e.message)
            self._handle_entity_error(context, pool.id,
                                      loadbalancer_id=lb.id)
            raise e


class MemberManager(EntityManager):
    """MemberManager class handles Neutron LBaaS pool member CRUD."""

    def __init__(self, driver):
        super(MemberManager, self).__init__(driver)
        self.model = models.MemberV2

    @log_helpers.log_method_call
    def recreate(self, context, member):
        self.create(context, member)

    @log_helpers.log_method_call
    def create(self, context, member):
        """Create a member."""

        self._log_entity(member)

        driver = self.driver
        lb = member.pool.loadbalancer
        self._check_provisioning_status(lb, status="PENDING_UPDATE")

        # Refuse to create member along with another tenant's subnet
        subnet = driver.plugin.db._core_plugin.get_subnet(
            context, member.subnet_id
        )
        if member.tenant_id != subnet["tenant_id"]:
            network = driver.plugin.db._core_plugin.get_network(
                context, subnet["network_id"]
            )
            if not network["shared"]:
                raise Exception(
                    "Member and subnet are not belong to the same tenant"
                )

        the_port_id = None
        driver = self.driver

        filters = {
            'device_owner': ['network:f5lbaasv2'],
            'fixed_ips': {'subnet_id': [member.subnet_id]}
        }
        LOG.debug('fetching certain ports details:')
        all_ports = driver.plugin.db._core_plugin.get_ports_count(
            context, filters
        )
        LOG.debug("all_ports length:")
        LOG.debug(all_ports)

        if all_ports < 1:
            # agent_host, service = self._setup_crud(context, lb, member)
            agent_host = 'temp'
            LOG.info('running here')
            if member.attached_to_loadbalancer() and lb:
                LOG.info('scheduing here instead')
                this_agent = self.driver.scheduler.schedule(
                    self.driver.plugin,
                    context,
                    lb.id,
                    self.driver.env
                )
                LOG.info(this_agent)
                agent_host = this_agent.get('host')
                LOG.info(agent_host)

            p = driver.plugin.db._core_plugin.create_port(context, {
                'port': {
                    'tenant_id': subnet['tenant_id'],
                    'network_id': subnet['network_id'],
                    'mac_address': n_const.ATTR_NOT_SPECIFIED,
                    'fixed_ips': n_const.ATTR_NOT_SPECIFIED,
                    'device_id': member.id,
                    'device_owner': 'network:f5lbaasv2',
                    'admin_state_up': member.admin_state_up,
                    'name': 'pool_port_' + member.id,
                    portbindings.HOST_ID: agent_host}})
            LOG.debug('the port created here is: %s' % p)
            the_port_id = p['id']

        api_dict = member.to_dict(pool=False)

        def append_pools_monitors(context, loadbalancer, service):
            self._append_pools_monitors(context, service, member.pool)

        LOG.info('the_port_id is:')
        LOG.info(the_port_id)

        try:
            if cfg.CONF.f5_driver_perf_mode in (2, 3):
                # Utilize default behavior to append all members
                self._call_rpc(
                    context, lb, member, api_dict, 'create_member',
                    append_listeners=lambda *args: None,
                    append_pools_monitors=append_pools_monitors,
                    append_l7policies_rules=lambda *args: None,
                    the_port_id=the_port_id
                )
            else:
                self._call_rpc(
                    context, lb, member, api_dict, 'create_member',
                    the_port_id=the_port_id
                )
        except Exception as e:
            LOG.error("Exception: member create: %s" % e.message)
            self._handle_entity_error(context, member.id,
                                      loadbalancer_id=lb.id)
            raise e

    # only for MIGU
    @log_helpers.log_method_call
    def create_bulk(self, context, members):
        """Create members."""
        subnets = []
        the_port_ids = []

        LOG.info("create_bulk start for members: %s" % members)

        if not members:
            LOG.error("no members found in members. Just return.")
            return

        driver = self.driver
        lb = members[0].pool.loadbalancer

        for member in members:
            if member.subnet_id not in subnets:
                the_filter = {
                    'device_owner': ['network:f5lbaasv2'],
                    'fixed_ips': {'subnet_id': [member.subnet_id]}
                }
                LOG.debug('fetching ports details:')
                all_ports = driver.plugin.db._core_plugin.get_ports_count(
                    context, the_filter
                )
                LOG.info("all_ports length:")
                LOG.info(all_ports)
                if all_ports < 1:
                    subnet = driver.plugin.db._core_plugin.get_subnet(
                        context, member.subnet_id
                    )
                    LOG.info("end getting subnet")

                    agent = self.driver.scheduler.schedule(
                        self.driver.plugin, context, lb.id, self.driver.env
                    )
                    LOG.info("end scheduling agent")
                    LOG.info(agent)

                    agent_host = agent['host']
                    p = driver.plugin.db._core_plugin.create_port(context, {
                        'port': {
                            'tenant_id': subnet['tenant_id'],
                            'network_id': subnet['network_id'],
                            'mac_address': n_const.ATTR_NOT_SPECIFIED,
                            'fixed_ips': n_const.ATTR_NOT_SPECIFIED,
                            'device_id': member.id,
                            'device_owner': 'network:f5lbaasv2',
                            'admin_state_up': member.admin_state_up,
                            'name': 'fake_pool_port_' + member.id,
                            portbindings.HOST_ID: agent_host}})
                    LOG.info('the port created here is: %s' % p)
                    the_port_ids.append(p['id'])

                api_dict = member.to_dict(pool=False)
                subnets.append(member.subnet_id)

        self._call_rpc(context, lb, member, api_dict, 'create_member',
                       the_port_ids=the_port_ids, multiple=True)

        LOG.info("create_bulk for members end.")


class HealthMonitorManager(EntityManager):
    """HealthMonitorManager class handles Neutron LBaaS monitor CRUD."""

    def __init__(self, driver):
        super(HealthMonitorManager, self).__init__(driver)
        self.model = models.HealthMonitorV2

    @log_helpers.log_method_call
    def create(self, context, health_monitor):
        """Create a health monitor."""

        self._log_entity(health_monitor)

        lb = health_monitor.pool.loadbalancer
        api_dict = health_monitor.to_dict(pool=False)

        def append_pools_monitors(context, loadbalancer, service):
            self._append_pools_monitors(context, service, health_monitor.pool)

        try:
            if cfg.CONF.f5_driver_perf_mode in (2, 3):
                # Utilize default behavior to append all members
                self._call_rpc(
                    context, lb, health_monitor, api_dict,
                    'create_health_monitor',
                    append_listeners=lambda *args: None,
                    append_pools_monitors=append_pools_monitors,
                    append_l7policies_rules=lambda *args: None
                )
            else:
                self._call_rpc(context, lb, health_monitor, api_dict,
                               'create_health_monitor')
        except Exception as e:
            LOG.error("Exception: health monitor create: %s" % e.message)
            self._handle_entity_error(context, health_monitor.id,
                                      loadbalancer_id=lb.id)
            raise e


class L7PolicyManager(EntityManager):
    """L7PolicyManager class handles Neutron LBaaS L7 Policy CRUD."""

    def __init__(self, driver):
        super(L7PolicyManager, self).__init__(driver)
        self.model = models.L7Policy

    @log_helpers.log_method_call
    def recreate(self, context, policy):
        self.create(context, policy)

    @log_helpers.log_method_call
    def create(self, context, policy):
        """Create an L7 policy."""

        self._log_entity(policy)

        lb = policy.listener.loadbalancer
        api_dict = policy.to_dict(listener=False, rules=False)

        def append_listeners(context, loadbalancer, service):
            self._append_listeners(context, service, policy.listener)

        def append_pools_monitors(context, loadbalancer, service):
            self._append_pools_monitors(
                context, service, policy.listener.default_pool)

        try:
            if cfg.CONF.f5_driver_perf_mode in (2, 3):
                # Utilize default behavior to load policies and rules
                # Listener may have default pool
                # Utilize default behavior to load members
                self._call_rpc(
                    context, lb, policy, api_dict, 'create_l7policy',
                    append_listeners=append_listeners,
                    append_pools_monitors=append_pools_monitors
                )
            else:
                self._call_rpc(context, lb, policy, api_dict,
                               'create_l7policy')
        except Exception as e:
            LOG.error("Exception: l7policy create: %s" % e.message)
            self._handle_entity_error(context, policy.id,
                                      loadbalancer_id=lb.id)
            raise e


class L7RuleManager(EntityManager):
    """L7RuleManager class handles Neutron LBaaS L7 Rule CRUD."""

    def __init__(self, driver):
        super(L7RuleManager, self).__init__(driver)
        self.model = models.L7Rule

    @log_helpers.log_method_call
    def create(self, context, rule):
        """Create an L7 rule."""

        self._log_entity(rule)

        lb = rule.policy.listener.loadbalancer
        api_dict = rule.to_dict(policy=False)

        def append_listeners(context, loadbalancer, service):
            self._append_listeners(context, service, rule.policy.listener)

        def append_pools_monitors(context, loadbalancer, service):
            self._append_pools_monitors(
                context, service, rule.policy.listener.default_pool)

        try:
            if cfg.CONF.f5_driver_perf_mode in (2, 3):
                # Utilize default behavior to load policies and rules
                # Listener may have default pool
                # Utilize default behavior to load members
                self._call_rpc(
                    context, lb, rule, api_dict, 'create_l7rule',
                    append_listeners=append_listeners,
                    append_pools_monitors=append_pools_monitors
                )
            else:
                self._call_rpc(context, lb, rule, api_dict, 'create_l7rule')
        except Exception as e:
            LOG.error("Exception: l7rule create: %s" % e.message)
            self._handle_entity_error(context, rule.id,
                                      loadbalancer_id=lb.id)
            raise e
